/**************************************************************
 * fetchlyric_winampcn.c - 
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 **************************************************************/


#include <stdio.h>
#include <curl/curl.h>
#include <string.h>
#include <errno.h>
#include <expat.h>
#include <unistd.h>
#include <fcntl.h>
/* #include <iconv.h> */
/* #include <sys/mman.h> */
#include "common.h"
#include "configfile.h"
#include "encoding_convert.h"
#include "fetchlyric_winampcn.h"


struct user_data{
  struct lyric_url_winampcn *urllist;
  struct lyric_url_winampcn *tmp;
};


FILE *url_open(char *url)
{
  CURL *h;
  CURLcode curlerr;
  FILE *fp=NULL;
  
  if(!url) {
    DBGMSG("** %s : url not psecified.\n",__FUNCTION__);
    return NULL;
  }

  curl_global_init(CURL_GLOBAL_ALL);
  h=curl_easy_init();
  if(!h) {
    DBGMSG("** %s : cannt init curllib\n",__FUNCTION__);
    curl_global_cleanup();
    return NULL;
  }

  curl_easy_setopt(h,CURLOPT_URL,url);
  fp=tmpfile();
  if(!fp) {
    DBGMSG("** %s : cannt open a temprary file.\n",__FUNCTION__);
    return NULL;
  }
  curl_easy_setopt(h,CURLOPT_WRITEDATA,fp);

  curlerr=curl_easy_perform(h);
  curl_easy_cleanup(h);
  curl_global_cleanup();
  if(curlerr) {
    DBGMSG("** %s : error fetching data:error code:%d\n",
		   __FUNCTION__,curlerr);
    fclose(fp);
    return NULL;
  }
  return fp;
}




/*
 *@data will be a pointer to struct user_data
 */
static void start(void *data, const char *el, const char **attr)
{
  struct lyric_url_winampcn *lp;
  struct user_data *ud;
  char **tmp;
  int i;

  ud = (struct user_data *)data;
  if(! strcmp(el, "LyricUrl"))  {
    lp = checked_malloc(sizeof(struct lyric_url_winampcn));
	ud->tmp = lp;

	for(i = 0; attr[i]; i += 2)  {
	  if(! strcmp(attr[i],"Artist"))
		tmp = &lp->artist;
	  else if(! strcmp(attr[i], "SongName"))
		tmp = &lp->songname;
	  else if(! strcmp(attr[i], "album"))
		tmp = &lp->album;
	  else
		continue;
   
	  *tmp = (char *)checked_malloc(strlen(attr[i + 1]) + 1);
	  strcpy(*tmp, attr[i + 1]);
	}
  }
}


static void end(void *data, const char *el)
{
  //  static struct lyric_url_winampcn *urllist=NULL;
  struct user_data *ud;
  struct lyric_url_winampcn *tmp;

  ud = (struct user_data *)data;
  if(! ud->tmp)
    return;
  if(ud->urllist) {
    tmp = ud->urllist;
    while(tmp->next)
      tmp = tmp->next;
    tmp->next = ud->tmp;
  }else
    ud->urllist = ud->tmp;

  ud->tmp = NULL;
}


static void datahandler(void *data, const char *ch,int len)
{
  struct user_data *ud;
  char *buf;

  ud = (struct user_data *)data;
  if(! ud->tmp)
    return;
  buf = checked_malloc(len + 1);
  strncpy(buf, ch, len);
  buf[len]= '\0';
  ud->tmp->url = buf;
  return;
}


struct lyric_url_winampcn *parsexml(FILE *fp, const char *encoding)
{
  XML_Parser parser;
  struct user_data data={NULL,NULL};
  /*   iconv_t cd; */
  /*   int needconv=0; */
  /*   char *buf,*bufin,*bufout,*frombuf,*tobuf; */
  /*   int tolen,fromlen; */
  /*   int flen,ret; */
  void *buffer;
  int ret, read;

  /*   if(encoding) */
  /*     if(strcmp(encoding,"utf-8")) { */
  /*       cd=iconv_open("utf-8",encoding); */
  /*       if(cd == (XML_Parser)-1) { */
  /* 	fprintf(stderr,"**can not convert encoding %s to utf-8:%s!\n", */
  /* 		encoding,strerror(errno)); */
  /* 	return NULL; */
  /*       } */
  /*       needconv=1; */
  /*     } */

  parser = XML_ParserCreate("UTF-8");
  if(! parser) {
    fprintf(stderr,"** XML parser error!\n");
    return NULL;
  }

  XML_SetUserData(parser, (void *)&data);
  XML_SetElementHandler(parser, start, end);
  XML_SetCharacterDataHandler(parser, datahandler);
  XML_SetEncoding(parser, "UTF-8");

  
  if(encoding)
    if(strcasecmp(encoding, "utf-8"))
      if(! (fp = encoding_convert_const(fp, "gbk", "utf-8", FALSE)))  {
		fprintf(stderr, "** error convert encoding!\n");
		return NULL;
      }

  /*   fseek(fp,0,SEEK_END); */
  /*   flen=ftell(fp); */
  /*   bufin=mmap(NULL,flen,PROT_READ, */
  /* 	     MAP_PRIVATE,fileno(fp),0); */
  /*   if(bufin == MAP_FAILED) { */
  /*     fprintf(stderr,"** file reading error while parsing xml!\n"); */
  /*     return NULL; */
  /*   } */
  /*   buf=bufin; */

  /*   /\* converting encodings if needed*\/ */
  /*   if(needconv) { */
  /*     frombuf=bufin; */
  /*     fromlen=flen; */

  /*     bufout=(char *)checked_malloc(2*flen); */
  /*     tobuf=bufout; */
  /*     tolen=2*flen; */
  /*     ret=iconv(cd,&frombuf,&fromlen,&tobuf,&tolen); */
  /*     iconv_close(cd); */
  /*     if(ret == (size_t)-1) { */
  /* 	fprintf(stderr,"**can not convert encoding %s to utf-8:%s!\n", */
  /* 		encoding,strerror(errno)); */
  /* 	free(bufout); */
  /* 	munmap(bufin,flen); */
  /* 	return NULL; */
  /*     } */
  /*     buf=bufout; */
  /*   } */

  /*   munmap(bufin,flen); */

  /*   ret=XML_Parse(parser,buf,2*flen-tolen,1); */
  /*   if(ret == XML_STATUS_ERROR) { */
  /*     fprintf(stderr,"**xml parsing error!\n"); */
  /*     free(buf); */
  /*     return NULL; */
  /*   } */

  rewind(fp);

  while(1)  {
    buffer = XML_GetBuffer(parser, 1024);
    if(! buffer)  {
      write(2, "** OOM!\n", 8);
      exit(-1);
    }

    read = fread(buffer, 1, 1024, fp);

    ret = XML_ParseBuffer(parser, read, feof(fp));

    if(ret == XML_STATUS_ERROR)  {
      fprintf(stderr, "** error parsing xml:%s:%d:%d\n",
			  XML_ErrorString(XML_GetErrorCode(parser)),
			  XML_GetCurrentLineNumber(parser),
			  XML_GetCurrentColumnNumber(parser));
      XML_ParserFree(parser);
      return NULL;
    }

    if(feof(fp))
      break;
  }

  XML_ParserFree(parser);

  return data.urllist;
}


static void free_lyric_url_winampcn(struct lyric_url_winampcn *urls)
{
  struct lyric_url_winampcn *tmp = urls;

  if(! urls)
    return;

  while(tmp)  {
    tmp = urls->next;
    if(urls->artist)
      free(urls->artist);
    if(urls->songname)
      free(urls->songname);
    if(urls->album)
      free(urls->album);
    if(urls->url)
      free(urls->url);
    urls = tmp;
  }
}


static gboolean __is_illegal(gchar c, const gchar *str)
{
  while(*str)
	if(c == *str++)
	  return TRUE;

  return FALSE;
}

static gchar  *__sanify(gchar *str)
{
  gchar *p, *i, *j;

  if(! str)
    return NULL;

  p = g_new0(gchar, strlen(str) + 1);
  i = p;
  j = str;
  while(*j)  {
    if(! __is_illegal(*j, " \t~`!@#$%^&*()-_+=|\\{}[]:\";\'<>?,./"))  {
	  *i = *j;
	  i++;
	}
    j++;
  }

  strcpy(str, p);
  g_free(p);

  return str;
}


static gchar *construct_query_url(const gchar *title, const gchar *artist)
{
  if(! title)
    return NULL;

#ifdef WINAMP_URL_GB2312
  gchar *url, *t, *a = NULL;
  t = g_convert(title, strlen(title), "gb2312", "utf8", NULL, NULL, NULL);
  if(! t)
    return NULL;
  if(artist)
    a = g_convert(artist, strlen(artist), "gb2312", "utf8", NULL, NULL, NULL);
  __sanify(t);
  __sanify(a);
  url = g_strdup_printf("%ssong=%s&artist=%s&lsong=%s&Datetime=20060601", 
						WINAMPCN_URL, t, a ? : "\"\"", t);
  if(t)
    g_free(t);
  if(a)
    g_free(a);
  return url;
#elif defined(WINAMPCN_URL_UTF8)
  return g_strdup_printf("%ssong=%s&artist=%s&lsong=%s&Date=20060601",
						 WINAMPCN_URL, __sanify(title), __sanify(artist) ? : "\"\"", title);

#else
#error  "** music meta encoding in url must be defined!";
#endif
}


static GSList   *winampcn_fetchlyric_get_list(struct fetchlyric_plugin *plugin, const gchar *title, const gchar *artist)
{
  GSList *list = NULL;
  gchar *url;
  FILE *xml_urllist;
  struct lyric_url_winampcn *urllist, *iter;
  struct lyric_url *lyricurl;

  if(! plugin)
    return NULL;

  url = construct_query_url(title, artist);
  if(! url)
    return NULL;

  xml_urllist = url_open(url);
  g_free(url);
  if(! xml_urllist)
    return NULL;

#ifdef WINAMPCN_URL_UTF8
  urllist = parsexml(xml_urllist, "utf-8");
#elif defined(WINAMPCN_URL_GB2312)
  urllist = parsexml(xml_urllist, "gb2312");
#else
#error  "** music meta encoding in url must be defined!";
#endif

  iter = urllist;
  while(iter)  {
	lyricurl = g_new(struct lyric_url, 1);
	lyricurl->title = g_strdup(iter->songname);
	lyricurl->artist = g_strdup(iter->artist); 
	lyricurl->album = g_strdup(iter->album);
	lyricurl->plugin = plugin;
	lyricurl->url = g_strdup(iter->url);

	list = g_slist_append(list, lyricurl);
	iter = iter->next;
  }

  free_lyric_url_winampcn(urllist);

  return list;
}

static gchar *get_datadir(const gchar *basedir, const gchar *category)
{
  const gchar *_basedir, *_category;
  gchar *path;

  if(basedir)
	_basedir = basedir;
  else
	_basedir = g_getenv("HOME");

  if(! _basedir)
	_basedir = "/tmp";

  if(category)
	_category = category;
  else
	_category = LYRIC_DIR;

  path = g_strdup_printf("%s/%s", _basedir, _category);
  if(! g_mkdir_with_parents(path, 0755))
	return path;

  g_free((void *)path);
  return NULL;
}


static gboolean put_data(const gchar *basedir, const gchar *category,
						 const gchar *key, FILE *lyric, gboolean overwrite)
{
  gchar *dir, *path;
  FILE *fp;
  char buf[1024];
  int i,j;

  if(! key || ! lyric)
	return FALSE;

  dir = get_datadir(basedir, category);
  if(! dir)
	return FALSE;
  path = g_strdup_printf("%s/%s", dir, key);
  g_free(dir);
  if(overwrite)  {
	if(g_file_test(path, G_FILE_TEST_IS_DIR))
	  if(g_rmdir(path))  {
		g_free(path);
		return FALSE;
	  }
  }else
	if(g_file_test(path, G_FILE_TEST_EXISTS))  {
	  g_free(path);
	  return FALSE;
	}

  fp = fopen(path, "w+");
  g_free(path);
  if(! fp)
	return FALSE;
  rewind(lyric);
  while(i = fread(buf, 1, 1024, lyric))  {
	j = 0;	
	while(j < i)
	  j += fwrite(buf + j, 1, i - j, fp);
  }

  fclose(fp);
  return TRUE;
}


static void   cache_lyric(FILE *lyric, const gchar *title, const gchar *artist)
{
  gchar *key;
  gboolean ret;


  if(! lyric)
	return;
  key = g_strdup_printf("%s - %s.lrc", title, artist ? : "");
  ret =  put_data(NULL, ".lyrics", key, lyric, TRUE);
#ifndef NVERBOSE
  if(! ret)
	printf("-- Failed to cache lyric to local.\n");
#endif
  g_free(key);
}


static FILE   *winampcn_fetchlyric_get_from_url(struct fetchlyric_plugin *plugin, struct lyric_url *url)
{
  FILE *lyric;

  if(! plugin || ! url || plugin != url->plugin)
	return NULL;

  lyric = url_open((char *)url->url);
#ifdef WINAMPCN_URL_UTF8
  cache_lyric(lyric, url->title, url->artist);
  return lyric;
#elif defined(WINAMPCN_URL_GB2312)
  FILE *ret = NULL;
  if(lyric)  {
	ret  = encoding_convert_const(lyric, "gb2312", "utf-8", FALSE);
	fclose(lyric);
  }
  cache_lyric(ret, url->title, url->artist);
  return ret;
#else
#error  "** music meta encoding in url must be defined!";
#endif
}


static gboolean __slist_find(gconstpointer a, gconstpointer b)
{
  struct lyric_url *lyricurl = (struct lyric_url *)a;
  struct fetchlyric_plugin *plugin = (struct fetchlyric_plugin *)b;

  return !(lyricurl->plugin == plugin);
}


static void __free_lyric_url(struct lyric_url *url)
{
  if(! url)
	return;

  if(url->title)
	free((void *)url->title);
  if(url->artist)
	free((void *)url->artist);
  if(url->album)
	free((void *)url->album);
  if(url->url)
	free(url->url);
  g_free(url);
}


static GSList    *winampcn_fetchlyric_free_list(struct fetchlyric_plugin *plugin, GSList *list)
{
  struct lyric_url *lyricurl;
  GSList *found;

  if(! list)
	return NULL;
  while(found = g_slist_find_custom(list, plugin, __slist_find))  {
	lyricurl = (struct lyric_url *)found->data;
	list = g_slist_remove(list, lyricurl);
	__free_lyric_url(lyricurl);
  }

  return list;
}


static FILE     *winampcn_fetchlyric_get(struct fetchlyric_plugin *plugin, const gchar *title, const gchar *artist)
{
  GSList *lyriclist;
  void *url;

  lyriclist = winampcn_fetchlyric_get_list(plugin, title, artist);
  if(! lyriclist)
	return NULL;

  url = ((struct lyric_url *)(lyriclist->data))->url;
  return winampcn_fetchlyric_get_from_url(plugin, url);
}


static struct fetchlyric_plugin  *winampcn_init(int argc, char **argv)
{
  struct fetchlyric_plugin *plugin;

  plugin = g_new0(struct fetchlyric_plugin, 1);

  plugin->fetchlyric_get_list = winampcn_fetchlyric_get_list;
  plugin->fetchlyric_get_from_url = winampcn_fetchlyric_get_from_url;
  plugin->fetchlyric_free_list = winampcn_fetchlyric_free_list;
  plugin->fetchlyric_get = winampcn_fetchlyric_get;

  return plugin;
}


static void                       winampcn_exit(struct fetchlyric_plugin *plugin)
{
  if(plugin)
	g_free(plugin);
}


struct fetchlyric_plugin_descrip FETCHLYRIC_EXPORT_SYMBOL = {
  .init = winampcn_init,
  .exit = winampcn_exit,

  .name = "winampcn",
  .description = "search and fetch lyrics from winampcn website.",
  .author = "X Q",
  .mail = "crs.chin@gmail.com",
  .version = "0.1",
  .islocal = 0
};

///////////////////// testing part /////////////////////

#ifdef TEST_XMLPARSER

int main()
{
  struct lyric_url_winampcn *list,*t;

  FILE *fp=url_open("http://www.winampcn.com/lrceng/get.aspx?song=\"\"&artist=\"\"&lsong=healtheworld&prec=1&Datetime=20060601");
  if(! fp)  {
	printf("Failed to get xml\n");
	return 1;
  }
#ifndef NDEBUG
  rewind(fp);
  char xxxx[1024];
  int i;
  do{
	i = fread(xxxx, 1, 1024, fp);
	printf("read byte: %d\n", i);
	if(! i)
	  break;
	printf("%*s", i, xxxx);
  }while(1);
  rewind(fp);
#endif

  if(fp)
	list=parsexml(fp,"utf-8");

  t=list;
  while(t!=NULL) {
	printf("SONGNAME:%s  URL:%s\n",t->songname,t->url);
	t=t->next;
  }

  return 0;
}

#endif


#ifdef TEST_NETLIB

int main()
{
  FILE *fp;
  char *s="http://www.google.com/";

  fp=url_open(s);
  if(!fp) {
	printf("**error geting %s \n",s);
	return 1;
  }
  rewind(fp);
  do
	putchar(getc(fp));
  while(!feof(fp));
  close(fp);
}

#endif  
